
module ActiveTest
  
  class ActiveTestSubjectError < ActiveTestError #:nodoc:
  end
  
  # == Subjects
  #
  # Each type of test, whether it is for a controller, ActiveRecord model, view or helper,
  # is considered the subject of an ActiveTest. Subjects extend ActiveTest::Base to allow
  # specific forms of testing, such as controller, record, stress or integration tests. They
  # come with an array of methods and attachments into both the Test::Unit and Rails libraries
  # to allow DRY or rich testing.
  #
  # As a general rule, each subject should be its own suite and would look something like this:
  #
  #   class SessionControllerTest < ActiveTest::Controller
  #
  #     setup
  #     succeeds_on :index
  #     assigns_records_on :index
  #
  #   end
  #
  # The model of the controller is automatically determined (or overridable with an option
  # to setup) and behaviours (see below) take the place of your most common test cases. Edge
  # cases are still defined in the usual way for Test::Unit::TestCase.
  #
  # Each subject contains a suite of behaviours which you can use out of the box or modify
  # to your specific purposes. Each one generates unique method names and atomic tests to
  # prevent ActiveTest from being too invasive.
  #
  # == Behaviours
  #
  # Instead of writing highly repetitive test cases, you can use dynamic class methods. In
  # ActiveTest, these dynamic methods are called 'behaviours', since those are what you are trying
  # to test and document in your test suites. They are meant purely to generate those test cases
  # which repeat frequently. Each type of behaviour responds to a hash of options which effect
  # the kind of test case it generates. Each behaviour maps to a very simple, atomic test with
  # an unique name (which the provided agiledox rake task can read).
  #
  # An example of a behaviour is 'succeeds_on' from the ActiveTest::Controller subject. This
  # behaviour test that a controller responds to a specific action with a positive. It is the
  # simple test that 'if this action exists, it should give 200 and display a page based on the
  # action's template'.
  #
  # Each behaviour is called as a class method and uses simple English to facilitate documentation.
  # For example:
  #
  #     succeeds_on :index
  #
  # In the case of +succeeds_on+, you can also give a hash of options, for example the +:parameters+
  # option, which tells it to send addition information in its test request:
  #
  #     succeeds_on :index, :parameters => { :category => 'shirts' }
  #
  # This will test that +get :index, :category => 'shirts'+ gives a 200 response and renders the
  # +index.rhtml+ template without an error.
  #
  # == Redundant Testing
  #
  # In the above example described in +Behaviours+, we see a technique of testing which has
  # hitherto been too tedious to countenance. It tests for additional parameters even when
  # the result should be unaffected. This is what ActiveTest calls 'redundant testing',
  # because in a following test, you test that the parameters do, in fact, change the results.
  # The benefit of this technique is that you know with certainty your code works at every
  # step of the way until a failure, with each test completely atomic in its assertions.
  #
  #     succeeds_on :index
  #     succeeds_on :index, :parameters => { :category => 'shirts' }
  #     succeeds_on :index, :parameters => { :category => 'shirts' },
  #                         :requirements => { :category => 'shirts' }
  #
  # This will produce three test cases, each slightly different, with only the first and third
  # making new assertions. You can, of course, just use the third one and get the benefit
  # of the first two, but then you must track down whether it is your parameters or the returned
  # value which is causing the problem. With redundant testing, you know immediately.
  #
  # == Anatomy of Behaviours
  #
  # Behaviours are deliberately simple. You can extend them quite easily or write your own. The
  # reason for keeping them simple is just to avoid errors caused by ActiveTest itself -- this
  # caution alone is reason enough to not cringe at metaprogramming tests. It is possible to
  # remove some repetition and make tests a little more fun -- even more efficient.
  #
  # It will not fill everyone with warm fuzzy feelings knowing that their tests are in the
  # hands of a chameleonic framework. To ease some of the tension, the anatomy of the call
  # stack for a dynamic method is explained below. It's quite elementary.
  #
  # Here is the call stack in shorthand:
  #
  #     succeeds_on(action = :index, options = { :parameters => {:category => 'shirts'} })
  #       => define_method "test_should_succeed_on_index_with_parameters_of_category_as_shirts"
  #         parameters = options[:parameters] || {}
  #         => call_index_action(action = :index, parameters = {})
  #           => get action, parameters
  #         => assert_index_success
  #           => method_missing("assert_index_success")
  #             => assert_restful_get_success(action = :index)
  #               => assert_response(:success)
  #               => assert_template(action.to_s)
  #
  # So, you see, it is very easy to overwrite any stage of the testing process and, in fact,
  # ActiveTest does its absolute best at getting out of your way when you want. If you have an action
  # called 'track' that needs a special response, write a custom +assert_track_success+.
  # If, on the other hand, the operation needs to perform a special action, you can define
  # +call_track_action+ and use it as part of your dynamic methods. If you don't like the way
  # +succeeds_on+ or +fails_on+ handle things, you can rewrite them too in your +test_helper.rb+
  # or in the test suite itself. But please be sure to check the source of anything you alter
  # and follow the original interface.
  #
  class Subject < Base

    class << self

      # Defines a test method. It uses an unique name based on the prefix, core and
      # any additional specifics.
      def define_behavioral(prefix, core, specifics = {}, &block)

        proposed = "test_should_#{prefix.to_s.underscore}"
        proposed << "_#{core.to_s.underscore}" if core.respond_to?(:to_s)

        while method_defined?(proposed)
          if proposed =~ /^(.*)(_variant_)(\d+)$/
            proposed = "#{$1}#{$2}#{$3.to_i + 1}" 
          else
            proposed << "_variant_2"
          end
        end

        define_method proposed, &block

        proposed
      end
      
      alias_method :define_behaviour, :define_behavioral
      alias_method :define_behavior, :define_behavioral

    end
  end
end